#!/usr/bin/env python
# -*- coding: utf-8 -*-

'''
自定义web框架模块，包含请求和响应类的封装
'''

from lib.webconf import WEBCONF


class Request(object):
    '请求类封装'
    def __init__(self, environ):
        self._environ = environ
        self._form, self._form_dict = self._parse_form()
        self._query, self._query_dict = self._parse_query()

    def _parse_form(self):
        '解析post表单'
        import cgi
        import urllib2

        form_dict = cgi.parse(
                fp=self._environ['wsgi.input'],
                environ=self._environ)
        form = {}
        for keys, values in form_dict.iteritems():
            key = urllib2.unquote(keys)
            value = urllib2.unquote(values[0])
            form[key] = value
        return form, form_dict

    def _parse_query(self):
        '解析get表单'
        import cgi
        import urllib2

        query_dict = cgi.parse_qs(self._environ['QUERY_STRING'])
        query = {}
        for keys, values in query_dict.iteritems():
            key = urllib2.unquote(keys)
            value = urllib2.unquote(values[0])
            query[key] = value
        return query, query_dict

    @property
    def query(self):
        '获取get表单，不包含重复值'
        return self._query

    @property
    def querys(self):
        '获取post表单，包含重复值'
        return self._query_dict

    @property
    def form(self):
        '获取post表单，不包含重复值'
        return self._form

    @property
    def forms(self):
        '获取post表单，包含重复值'
        return self._form_dict

    @property
    def req_uri(self):
        '获取请求路径（含参数）'
        req_uri = self._environ['PATH_INFO']
        if self._environ.get('QUERY_STRING', ''):
            req_uri += '?' + self._environ['QUERY_STRING']
        return req_uri

    def __getitem__(self, key):
        '获取环境变量'
        return self._environ[key]

    def get_cookie(self, key):
        '获取请求包含的cookie'
        import Cookie

        try:
            cookie = Cookie.SimpleCookie(self._environ['HTTP_COOKIE'])
            value = cookie[key].value
        except(Cookie.CookieError, KeyError):
            value = None
        return value


class Response(object):
    '响应封装类'
    STATUS_TYPE = {
            'ok': '200 OK',
            'notfound': '404 NOTFOUND',
            'unauth': '200 OK',
            'notoken': '200 OK',
            'token_notmatch': '200 OK',
            'forbidden': '405 FORBIDDEN',
            'unknow': '500 Internal Server Error'}

    def __init__(self, start_response):
        import Cookie

        self._start_response = start_response
        self._status = self.STATUS_TYPE['ok']
        self._header = []
        self._cookie = Cookie.SimpleCookie()

    def __call__(self):
        cookie_header = self._cookie.output()
        if cookie_header:
            pos = cookie_header.split(': ')
            self.set_header(pos[0], pos[1])
        self._start_response(self._status, self._header)

    def set_header(self, key, value):
        '设置一个http头'
        self._header.append((key, value))

    def set_cookie(self, key, value, expir_sec=None):
        '设置一个cookie'
        import datetime

        self._cookie[key] = value
        self._cookie[key]['path'] = '/'
        self._cookie[key]['httponly'] = True

        if expir_sec:
            expir = datetime.datetime.now()
            expir += datetime.timedelta(seconds=expir_sec)
            expir_str = expir.strftime('%a, %d-%b-%Y %H:%M:%S PST')
            self._cookie[key]['expires'] = expir_str

    def clear_cookie(self, key):
        '清除一个cookie'
        self.set_cookie(key, '', -1)

    def set_status(self, status):
        '设置http响应状态码'
        if status in Response.STATUS_TYPE:
            self._status = Response.STATUS_TYPE[status]
        else:
            self._status = Response.STATUS_TYPE['unknow']


class WebApplication(object):
    'web框架封装'
    _HANDLE_NAME = None  # handle名称，用于模板前缀
    _PATH_PREFIX = None  # 一级路径名称，用于解析path

    def __init__(self, render=None, dispatcher=None):
        self._session = {}
        self._request = None
        self._response = None
        self._arg_dict = {}

        #可设置新分发器，必须为Dispatcher或其子类的实现
        if dispatcher:
            if not isinstance(dispatcher, Dispatcher):
                raise TypeError('It must be instance of Dispatcher.')
            self._dispatcher = dispatcher
        else:
            self._dispatcher = Dispatcher()
        self._dispatcher.route_compile()

        #可设置新渲染器，必须为Render或其子类的实现
        if render:
            if not isinstance(render, Render):
                raise TypeError('It must be instance of Render.')
            self._render = render
        else:
            self._render = Render()

    def __call__(self, environ, start_response):
        self._session = {}
        self._render.reset()

        self._request = Request(environ)
        self._response = Response(start_response)

        self.get_action()
        self.action()
        self._response.set_header('Content-Type', self._render.content_type)
        self._response()
        return [self._render(), ]

    def get_action(self):
        '根据请求路径确定执行的action'
        path_info = self._request['PATH_INFO']
        action_name, self.arg_dict = self._dispatcher(
                path_info, self._PATH_PREFIX)

        func_name = 'action_%s' % action_name
        if hasattr(self, func_name):
            self._render.args['tmpl_name'] = '%s_%s' % (
                    self._HANDLE_NAME, action_name)
            self.action = getattr(self, func_name)
        else:
            self.action = self.error

    def action(self):
        '执行具体的业务逻辑。抽象方法，运行时确定'
        raise NotImplementedError

    def error(self, error_type=None):
        '使用此函数标记http报文错误，渲染模板会被改变'
        if not error_type:
            error_type = 'notfound'
        self._render.mode = 'html'
        self._response.set_status(error_type)
        self._render.view['error_type'] = error_type
        self._render.args['tmpl_name'] = WEBCONF['error_name']

    def error_type(self, error_type=None):
        '处理逻辑中出现的错误，可以使用此函数标记'
        if not error_type:
            error_type = 'unkown error'
        self._render.view['error_type'] = error_type


class Dispatcher(object):
    '分发器封装'
    ACTION_TAG = 'action'

    def __init__(self, default_action=None):
        self._def_action = default_action or WEBCONF['default_action']
        self._routes = [('{%s}' % self.ACTION_TAG, None)]

        self._var_lists = []
        self._route_patterns = []

    def add_route(self, route, action):
        '增加一条路由规则'
        self._routes.append((route, action))

    def route_compile(self):
        '编译所有的路由规则，注意顺序'
        import re

        self._route_list = []

        pattern = re.compile(r'\{(.*?)\}')
        for route_str, action_str in self._routes:
            # 找出所有{}中的内容
            var_list = pattern.findall(route_str)
            route_new = '^%s$' % pattern.sub(r'(\w+)', route_str)
            route_pattern = re.compile(route_new)

            route_dict = {}
            route_dict['route_pattern'] = route_pattern
            route_dict['var_list'] = var_list
            route_dict['action_str'] = action_str

            self._route_list.append(route_dict)

        # 逆序，后进的路由规则优先级更高
        self._route_list.reverse()

    def __call__(self, path_info, prefix):
        '调用分发器，将传入的路径按规则分割'
        import re

        # 去掉一级路径
        path_info = path_info[len(prefix):].strip('/')
        if path_info == '':
            path_info = self._def_action

        arg_dict = {}
        action_name = ''

        # 按顺序进行路由匹配
        for route_dict in self._route_list:
            match = route_dict['route_pattern'].match(path_info)
            if not match:
                continue
            # 匹配到规则后的操作
            values = match.groups()
            var_list = route_dict['var_list']
            for i in xrange(len(values)):
                arg_dict[var_list[i]] = values[i]
            action_name = arg_dict.get(
                    self.ACTION_TAG, route_dict['action_str'])
            break

        return action_name, arg_dict


class RenderModeError(AttributeError):
    '定义了没有找到对应的渲染方法时的错误'
    pass


class Render(object):
    '渲染器封装'
    CONTENT_TYPE = {
            'html': 'text/html',
            'plain': 'text/plain',
            'url': 'text/plain',
            'json': 'application/json', }

    def __init__(self):
        self.view = None
        self.mode = None
        self.args = None

        self.reset()

    def __call__(self):
        '以不同形式进行渲染'
        render_str = 'render_' + self.mode
        if not hasattr(self, render_str):
            raise RenderModeError

        render_func = getattr(self, render_str)
        out_str = render_func()

        return out_str

    def reset(self):
        '重置渲染器的变量'
        self.view = {}
        self.mode = 'html'
        self.args = {}

    @property
    def content_type(self):
        '返回渲染响应的http报文类型，用于设置头响应'
        return self.CONTENT_TYPE[self.mode]

    def render_html(self):
        '以html形式渲染输出'
        from jinja2 import Environment
        from jinja2.loaders import FileSystemLoader
        from lib.validate import Filter

        encoding = WEBCONF['encoding']
        tmpl_path = WEBCONF['tmpl_path']

        env_loader = FileSystemLoader(tmpl_path)
        env = Environment(loader=env_loader, autoescape=True)
        tmpl_name = '%s.html' % self.args['tmpl_name']
        tmpl = env.get_template(tmpl_name)
        view_dict = Filter.to_unicode(self.view)
        outstr = tmpl.render(view_dict)

        return outstr.encode(encoding)

    def render_plain(self):
        '以纯文本形式输出'
        return self.view

    def render_url(self):
        '以URL编码形式输出'
        import urllib

        return urllib.urlencode(self.view)

    def render_json(self):
        '以JSON格式输出'
        import json
        from lib.validate import Filter

        view_json = Filter.to_str(self.view)
        return json.dumps(view_json)

if __name__ == '__main__':
    disp = Dispatcher()
    disp.add_route('{action}/id/{id}/date/{date}', None)
    disp.add_route('buy/proc', 'action_buyproc')
    disp.route_compile()

    print disp('item/buy/proc', 'item/')
    print disp('item/photo/id/123/date/adfkj', 'item/')
    print disp('item/photo', 'item/')
